package network;

public class NetworkPacket {
	
	/** @invariant fromTPA != null */
	private TPA fromTPA;
	/** @invariant toTPA != null */
	private TPA toTPA;
	/**
	 * @invariant payload != null
	 * @invariant 0 < payload.length <= Network.Constants.MAX_PAYLOAD_LENGTH
	 */
	private byte[] payload;
	private byte checksum;
	
	/**
	 * Creates a new networkpacket and calculates the checksum
	 * @param from the adress that this packet is send from
	 * @param to the adress that this packet is send to
	 * @param payload ht data to send
	 * @requires from != null
	 * @requires to != null
	 * @requires 0 < payload.length <= Network.Constants.MAX_PAYLOAD_LENGTH
	 * @ensures checkChecksum()
	 * @ensures getChecksum() == createChecksum()
	 */
	public NetworkPacket(TPA from, TPA to, byte[] payload){
		fromTPA = from;
		toTPA = to;
		if(payload.length > Constants.MAX_PAYLOAD_LENGTH)	
			throw new IllegalArgumentException("Payload length larger than Network.Constants.MAX_PAYLOAD_LENGTH");
		this.payload = payload;
		createChecksum();
	}
	
	/**
	 * calculates the checksum of this packet and sets it in the checksum field
	 * @ensures checkChecksum()
	 */
	public void createChecksum(){
		this.checksum = calcChecksum();
	}
	
	/**
	 * return whether or not the checksum in this packet corresponds with the 
	 * expected checksum
	 * @return true if the checksums match, false otherwise
	 */
	public boolean checkChecksum(){
		return this.checksum == calcChecksum();
	}
	
	/**
	 * returns the current Packet as a byte array (with the current checksum, which
	 * could be incorrect if the packet was created from a byte array)
	 * @return a byte array containing this packet
	 * @ensures \result.length == this.payload.length + 2
	 */
	public byte[] toByteArray(){
		byte[] res = new byte[payload.length + 2];
		res[0] = getAdressByte();
		res[1] = this.checksum;
		System.arraycopy(payload, 0, res, 2, payload.length);
		return res;
	}
	
	/**
	 * calculates the adress byte for this packet from the From and the To adress
	 * The results first 4 bits are the from adress, the second 4 bits are the to adress
	 * @return byte with from and to adress
	 */
	private byte getAdressByte(){
		//set right 4 bits (to adress)
		byte res = (byte) (0x0F & toTPA.getAdressAsByte());
		res |= ((byte) fromTPA.getAdressAsByte()) << 4;	
		return res;
	}
	
	/**
	 * calculates the expected checksum for this packet.
	 * @return the expected checksum for this packet
	 */
	private byte calcChecksum(){
		//the checksum is calculated by adding the first byte of the packet (the from and to-adress)
		//to the first byte of the payload and then adding the carry back in again.
		int adressByte = (((char) getAdressByte()) & 0x00FF);
		int frst = (((char) payload[0]) & 0x00FF);
		int inter = adressByte + frst;
		byte addInCarry = (byte) ((inter >> 8) & 0x01);
		byte checksum = (byte) (inter + addInCarry);
		return checksum;
	}
	
	
	public TPA getFromTPA() {
		return fromTPA;
	}
	public void setFromTPA(TPA fromTPA) {
		this.fromTPA = fromTPA;
	}
	public TPA getToTPA() {
		return toTPA;
	}
	public void setToTPA(TPA toTPA) {
		this.toTPA = toTPA;
	}
	public byte[] getPayload() {
		return payload;
	}
	public void setPayload(byte[] payload) {
		this.payload = payload;
	}
	public byte getChecksum() {
		return checksum;
	}
	protected void setChecksum(byte checksum) {
		this.checksum = checksum;
	}
	
	/**
	 * Creates a NetworkPacket from the provided byte array.
	 * @param packet the byte array to create the packet from
	 * @return a networkpacket from the provided byte array
	 * @requires packet != null
	 * @requires 1 < packet.length <= Network.Constant.MAX_PACKET_LENGTH
	 * @ensures \result != null
	 */
	public static NetworkPacket fromByteArray(byte[] packet){
		byte b0 = packet[0];
		//to is in the rightmost 4 bits
		TPA to = new TPA(b0);
		//from is in the leftmost 4 bits
		TPA from = new TPA(b0 >> 4);
		byte checksum = packet[1];
		byte[] payload = new byte[packet.length - 2];
		System.arraycopy(packet, 2, payload, 0, payload.length);
		
		NetworkPacket res = new NetworkPacket(from, to, payload);
		res.setChecksum(checksum);
		
		return res;
	}
	
}
